// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

//
// LinkContext.cs
//
// Author:
//   Jb Evain (jbevain@gmail.com)
//
// (C) 2006 Jb Evain
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using ILLink.Shared;
using ILLink.Shared.TypeSystemProxy;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Linker.Dataflow;
using Mono.Linker.Steps;

namespace Mono.Linker
{

    public class UnintializedContextFactory
    {
        public virtual AnnotationStore CreateAnnotationStore(LinkContext context) => new AnnotationStore(context);
        public virtual MarkingHelpers CreateMarkingHelpers(LinkContext context) => new MarkingHelpers(context);
        public virtual Tracer CreateTracer(LinkContext context) => new Tracer(context);
        public virtual EmbeddedXmlInfo CreateEmbeddedXmlInfo() => new();
        public virtual AssemblyResolver CreateResolver(LinkContext context) => new AssemblyResolver(context, new ReaderParameters());
    }

    public static class TargetRuntimeVersion
    {
        public const int NET5 = 5;
        public const int NET6 = 6;
    }

    public class LinkContext : IMetadataResolver, ITryResolveMetadata, ITryResolveAssemblyName, IDisposable
    {

        readonly Pipeline _pipeline;
        readonly Dictionary<string, AssemblyAction> _actions;
        readonly Dictionary<string, string> _parameters;
        int? _targetRuntime;

        readonly AssemblyResolver _resolver;
        TypeNameResolver? _typeNameResolver;

        readonly AnnotationStore _annotations;
        readonly CustomAttributeSource _customAttributes;
        readonly CompilerGeneratedState _compilerGeneratedState;
        readonly List<MessageContainer> _cachedWarningMessageContainers;
        readonly ILogger _logger;
        readonly Dictionary<AssemblyDefinition, bool> _isTrimmable;
        readonly UnreachableBlocksOptimizer _unreachableBlocksOptimizer;

        public Pipeline Pipeline
        {
            get { return _pipeline; }
        }

        public CustomAttributeSource CustomAttributes => _customAttributes;

        public CompilerGeneratedState CompilerGeneratedState => _compilerGeneratedState;

        public AnnotationStore Annotations => _annotations;

        public bool DeterministicOutput { get; set; }

        public int ErrorsCount { get; private set; }

        public string OutputDirectory { get; set; }

        public MetadataTrimming MetadataTrimming { get; set; }

        public AssemblyAction TrimAction { get; set; }

        public AssemblyAction DefaultAction { get; set; }

        public bool LinkSymbols { get; set; }

        public bool PreserveSymbolPaths { get; set; }

        public bool KeepComInterfaces { get; set; }

        public bool KeepMembersForDebugger { get; set; } = true;

        public bool IgnoreUnresolved { get; set; } = true;

        public bool EnableReducedTracing { get; set; }

        public bool KeepUsedAttributeTypesOnly { get; set; }

        public bool EnableSerializationDiscovery { get; set; }

        public bool DisableOperatorDiscovery { get; set; }

        public bool DisableGeneratedCodeHeuristics { get; set; }

        /// <summary>
        /// Option to not special case EventSource.
        /// Currently, values are hard-coded and does not have a command line option to control
        /// </summary>
        public bool DisableEventSourceSpecialHandling { get; set; }

        public bool IgnoreDescriptors { get; set; }

        public bool IgnoreSubstitutions { get; set; }

        public bool IgnoreLinkAttributes { get; set; }

        public Dictionary<string, bool> FeatureSettings { get; init; }

        public List<PInvokeInfo> PInvokes { get; private set; }

        public string? PInvokesListFile;

        public bool StripSecurity { get; set; }

        public Dictionary<string, AssemblyAction> Actions
        {
            get { return _actions; }
        }

        public AssemblyResolver Resolver
        {
            get { return _resolver; }
        }

        internal TypeNameResolver TypeNameResolver
            => _typeNameResolver ??= new TypeNameResolver(this, this);

        public ISymbolReaderProvider SymbolReaderProvider { get; set; }

        public bool LogMessages { get; set; }

        public MarkingHelpers MarkingHelpers { get; private set; }

        public KnownMembers MarkedKnownMembers { get; private set; }

        public WarningSuppressionWriter? WarningSuppressionWriter { get; set; }

        public HashSet<int> NoWarn { get; set; }

        public bool NoTrimWarn { get; set; }

        public Dictionary<int, bool> WarnAsError { get; set; }

        public bool GeneralWarnAsError { get; set; }

        public WarnVersion WarnVersion { get; set; }

        public UnconditionalSuppressMessageAttributeState Suppressions { get; set; }

        public Tracer Tracer { get; private set; }

        internal HashSet<string>? TraceAssembly { get; set; }

        public EmbeddedXmlInfo EmbeddedXmlInfo { get; private set; }

        public CodeOptimizationsSettings Optimizations { get; set; }

        public bool AddReflectionAnnotations { get; set; }

        public string? AssemblyListFile { get; set; }

        public List<IMarkHandler> MarkHandlers { get; }

        public Dictionary<string, bool> SingleWarn { get; set; }

        public bool GeneralSingleWarn { get; set; }

        public HashSet<string> AssembliesWithGeneratedSingleWarning { get; set; }

        public SerializationMarker SerializationMarker { get; }

        public LinkContext(Pipeline pipeline, ILogger logger, string outputDirectory)
            : this(pipeline, logger, outputDirectory, new UnintializedContextFactory())
        {
        }

        protected LinkContext(Pipeline pipeline, ILogger logger, string outputDirectory, UnintializedContextFactory factory)
        {
            ArgumentNullException.ThrowIfNull(logger);

            _pipeline = pipeline;
            _logger = logger;

            _resolver = factory.CreateResolver(this);
            _actions = new Dictionary<string, AssemblyAction>();
            _parameters = new Dictionary<string, string>(StringComparer.Ordinal);
            _customAttributes = new CustomAttributeSource(this);
            _compilerGeneratedState = new CompilerGeneratedState(this);
            _cachedWarningMessageContainers = new List<MessageContainer>();
            _isTrimmable = new Dictionary<AssemblyDefinition, bool>();
            OutputDirectory = outputDirectory;
            FeatureSettings = new Dictionary<string, bool>(StringComparer.Ordinal);

            SymbolReaderProvider = new DefaultSymbolReaderProvider(false);

            _annotations = factory.CreateAnnotationStore(this);
            MarkingHelpers = factory.CreateMarkingHelpers(this);
            SerializationMarker = new SerializationMarker(this);
            Tracer = factory.CreateTracer(this);
            EmbeddedXmlInfo = factory.CreateEmbeddedXmlInfo();
            MarkedKnownMembers = new KnownMembers();
            PInvokes = new List<PInvokeInfo>();
            Suppressions = new UnconditionalSuppressMessageAttributeState(this);
            NoWarn = new HashSet<int>();
            GeneralWarnAsError = false;
            WarnAsError = new Dictionary<int, bool>();
            WarnVersion = WarnVersion.Latest;
            MarkHandlers = new List<IMarkHandler>();
            GeneralSingleWarn = false;
            SingleWarn = new Dictionary<string, bool>();
            AssembliesWithGeneratedSingleWarning = new HashSet<string>();
            _unreachableBlocksOptimizer = new UnreachableBlocksOptimizer(this);

            const CodeOptimizations defaultOptimizations =
                CodeOptimizations.BeforeFieldInit |
                CodeOptimizations.OverrideRemoval |
                CodeOptimizations.UnusedInterfaces |
                CodeOptimizations.UnusedTypeChecks |
                CodeOptimizations.IPConstantPropagation |
                CodeOptimizations.UnreachableBodies |
                CodeOptimizations.RemoveDescriptors |
                CodeOptimizations.RemoveLinkAttributes |
                CodeOptimizations.RemoveSubstitutions |
                CodeOptimizations.RemoveDynamicDependencyAttribute |
                CodeOptimizations.OptimizeTypeHierarchyAnnotations |
                CodeOptimizations.SubstituteFeatureGuards;

            DisableEventSourceSpecialHandling = true;

            Optimizations = new CodeOptimizationsSettings(defaultOptimizations);
        }

        public void SetFeatureValue(string feature, bool value)
        {
            Debug.Assert(!string.IsNullOrEmpty(feature));
            FeatureSettings[feature] = value;
        }

        public bool HasFeatureValue(string feature, bool value)
        {
            return FeatureSettings.TryGetValue(feature, out bool fvalue) && value == fvalue;
        }

        public TypeDefinition? GetType(string fullName)
        {
            int pos = fullName.IndexOf(',');
            fullName = TypeReferenceExtensions.ToCecilName(fullName);
            if (pos == -1)
            {
                foreach (AssemblyDefinition asm in GetReferencedAssemblies())
                {
                    var type = asm.MainModule.GetType(fullName);
                    if (type != null)
                        return type;
                }

                return null;
            }

            string asmname = fullName.Substring(pos + 1);
            fullName = fullName.Substring(0, pos);
            AssemblyDefinition? assembly = Resolve(AssemblyNameReference.Parse(asmname));
            return assembly?.MainModule.GetType(fullName);
        }

        public AssemblyDefinition? TryResolve(string name)
        {
            return TryResolve(new AssemblyNameReference(name, new Version()));
        }

        public AssemblyDefinition? TryResolve(AssemblyNameReference name)
        {
            return _resolver.Resolve(name, probing: true);
        }

        public AssemblyDefinition? Resolve(IMetadataScope scope)
        {
            AssemblyNameReference reference = GetReference(scope);
            return _resolver.Resolve(reference);
        }

        public AssemblyDefinition? Resolve(AssemblyNameReference name)
        {
            return _resolver.Resolve(name);
        }

        public void RegisterAssembly(AssemblyDefinition assembly)
        {
            if (SeenFirstTime(assembly))
            {
                SafeReadSymbols(assembly);
                Annotations.SetAction(assembly, CalculateAssemblyAction(assembly));
            }
        }

        protected bool SeenFirstTime(AssemblyDefinition assembly)
        {
            return !_annotations.HasAction(assembly);
        }

        public virtual void SafeReadSymbols(AssemblyDefinition assembly)
        {
            if (assembly.MainModule.HasSymbols)
                return;

            if (SymbolReaderProvider == null)
                throw new InvalidOperationException("Symbol provider is not set");

            try
            {
                var symbolReader = SymbolReaderProvider.GetSymbolReader(
                    assembly.MainModule,
                    GetAssemblyLocation(assembly));

                if (symbolReader == null)
                    return;

                try
                {
                    assembly.MainModule.ReadSymbols(symbolReader);
                }
                catch
                {
                    symbolReader.Dispose();
                    return;
                }

                // Add symbol reader to annotations only if we have successfully read it
                _annotations.AddSymbolReader(assembly, symbolReader);
            }
            catch { }
        }

        public virtual ICollection<AssemblyDefinition> ResolveReferences(AssemblyDefinition assembly)
        {
            List<AssemblyDefinition> references = new List<AssemblyDefinition>();
            if (assembly == null)
                return references;

            foreach (AssemblyNameReference reference in assembly.MainModule.AssemblyReferences)
            {
                AssemblyDefinition? definition = Resolve(reference);
                if (definition != null)
                    references.Add(definition);
            }

            return references;
        }

        static AssemblyNameReference GetReference(IMetadataScope scope)
        {
            AssemblyNameReference reference;
            if (scope is ModuleDefinition moduleDefinition)
            {
                AssemblyDefinition asm = moduleDefinition.Assembly;
                reference = asm.Name;
            }
            else
                reference = (AssemblyNameReference)scope;

            return reference;
        }

        public void RegisterAssemblyAction(string assemblyName, AssemblyAction action)
        {
            _actions[assemblyName] = action;
        }

#if !FEATURE_ILLINK
        public void SetAction(AssemblyDefinition assembly, AssemblyAction defaultAction)
        {
            if (!_actions.TryGetValue(assembly.Name.Name, out AssemblyAction action))
                action = defaultAction;

            Annotations.SetAction(assembly, action);
        }
#endif
        public virtual AssemblyAction CalculateAssemblyAction(AssemblyDefinition assembly)
        {
            if (_actions.TryGetValue(assembly.Name.Name, out AssemblyAction action))
            {
                if (IsCPPCLIAssembly(assembly.MainModule) && action != AssemblyAction.Copy && action != AssemblyAction.Skip)
                {
                    LogWarning($"Invalid assembly action '{action}' specified for assembly '{assembly.Name.Name}'. C++/CLI assemblies can only be copied or skipped.", 2106, GetAssemblyLocation(assembly));
                    return AssemblyAction.Copy;
                }

                return action;
            }

            if (IsCPPCLIAssembly(assembly.MainModule))
                return DefaultAction == AssemblyAction.Skip ? DefaultAction : AssemblyAction.Copy;

            if (IsTrimmable(assembly))
                return TrimAction;

            return DefaultAction;

            static bool IsCPPCLIAssembly(ModuleDefinition module)
            {
                foreach (var type in module.Types)
                    if (type.Namespace == "<CppImplementationDetails>" ||
                        type.Namespace == "<CrtImplementationDetails>")
                        return true;

                return false;
            }
        }

        public bool IsTrimmable(AssemblyDefinition assembly)
        {
            if (_isTrimmable.TryGetValue(assembly, out bool isTrimmable))
                return isTrimmable;

            if (!assembly.HasCustomAttributes)
            {
                _isTrimmable.Add(assembly, false);
                return false;
            }

            foreach (var ca in assembly.CustomAttributes)
            {
                if (!ca.AttributeType.IsTypeOf<AssemblyMetadataAttribute>())
                    continue;

                var args = ca.ConstructorArguments;
                if (args.Count != 2)
                    continue;

                if (args[0].Value is not string key || !key.Equals("IsTrimmable", StringComparison.OrdinalIgnoreCase))
                    continue;

                if (args[1].Value is not string value || !value.Equals("True", StringComparison.OrdinalIgnoreCase))
                {
                    LogWarning(GetAssemblyLocation(assembly), DiagnosticId.InvalidIsTrimmableValue, args[1].Value.ToString() ?? "", assembly.Name.Name);
                    continue;
                }

                isTrimmable = true;
            }

            _isTrimmable.Add(assembly, isTrimmable);
            return isTrimmable;
        }

        public virtual AssemblyDefinition[] GetAssemblies()
        {
            var cache = _resolver.AssemblyCache;
            AssemblyDefinition[] asms = new AssemblyDefinition[cache.Count];
            cache.Values.CopyTo(asms, 0);
            return asms;
        }

        public AssemblyDefinition? GetLoadedAssembly(string name)
        {
            if (!string.IsNullOrEmpty(name) && _resolver.AssemblyCache.TryGetValue(name, out var ad))
                return ad;

            return null;
        }

        public string GetAssemblyLocation(AssemblyDefinition assembly)
        {
            return Resolver.GetAssemblyLocation(assembly);
        }

        public IEnumerable<AssemblyDefinition> GetReferencedAssemblies()
        {
            var assemblies = GetAssemblies();

            foreach (var assembly in assemblies)
                yield return assembly;

            var loaded = new HashSet<AssemblyDefinition>(assemblies);
            var toProcess = new Queue<AssemblyDefinition>(assemblies);

            while (toProcess.Count > 0)
            {
                var assembly = toProcess.Dequeue();
                foreach (var reference in ResolveReferences(assembly))
                {
                    if (!loaded.Add(reference))
                        continue;
                    yield return reference;
                    toProcess.Enqueue(reference);
                }
            }
        }

        public void SetCustomData(string key, string value)
        {
            _parameters[key] = value;
        }

        public bool HasCustomData(string key)
        {
            return _parameters.ContainsKey(key);
        }

        public bool TryGetCustomData(string key, [NotNullWhen(true)] out string? value)
        {
            return _parameters.TryGetValue(key, out value);
        }

        public void Dispose()
        {
            _resolver.Dispose();
        }

        public bool IsOptimizationEnabled(CodeOptimizations optimization, MemberReference context)
        {
            return Optimizations.IsEnabled(optimization, context?.Module.Assembly);
        }

        public bool IsOptimizationEnabled(CodeOptimizations optimization, AssemblyDefinition? context)
        {
            return Optimizations.IsEnabled(optimization, context);
        }

        public bool CanApplyOptimization(CodeOptimizations optimization, AssemblyDefinition context)
        {
            return Annotations.GetAction(context) == AssemblyAction.Link &&
                IsOptimizationEnabled(optimization, context);
        }

        public void LogMessage(MessageContainer message)
        {
            if (message == MessageContainer.Empty)
                return;

            if ((message.Category == MessageCategory.Diagnostic ||
                message.Category == MessageCategory.Info) && !LogMessages)
                return;

            if (WarningSuppressionWriter != null &&
                message.IsWarningMessage(out int? code) &&
                message.Origin?.Provider is Mono.Cecil.ICustomAttributeProvider provider)
                WarningSuppressionWriter.AddWarning(code.Value, provider);

            if (message.Category == MessageCategory.Error || message.Category == MessageCategory.WarningAsError)
                ErrorsCount++;

            _logger.LogMessage(message);
        }

        public void LogMessage(string message)
        {
            if (!LogMessages)
                return;

            LogMessage(MessageContainer.CreateInfoMessage(message));
        }

        public void LogDiagnostic(string message)
        {
            if (!LogMessages)
                return;

            LogMessage(MessageContainer.CreateDiagnosticMessage(message));
        }


        /// <summary>
        /// Display a warning message to the end user.
        /// This API is used for warnings defined in ILLink, not by custom steps. Warning
        /// versions are inferred from the code, and every warning that we define is versioned.
        /// </summary>
        /// <param name="text">Humanly readable message describing the warning</param>
        /// <param name="code">Unique warning ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of warnings and possibly add a new one</param>
        /// <param name="origin">Filename or member where the warning is coming from</param>
        /// <param name="subcategory">Optionally, further categorize this warning</param>
        public void LogWarning(string text, int code, MessageOrigin origin, string subcategory = MessageSubCategory.None)
        {
            WarnVersion version = GetWarningVersion();
            MessageContainer warning = MessageContainer.CreateWarningMessage(this, text, code, origin, version, subcategory);
            _cachedWarningMessageContainers.Add(warning);
        }

        /// <summary>
        /// Display a warning message to the end user.
        /// This API is used for warnings defined in ILLink, not by custom steps. Warning
        /// versions are inferred from the code, and every warning that we define is versioned.
        /// </summary>
        /// <param name="origin">Filename or member where the warning is coming from</param>
        /// <param name="id">Unique warning ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of warnings and possibly add a new one</param>
        /// <param name="args">Additional arguments to form a humanly readable message describing the warning</param>
        public void LogWarning(MessageOrigin origin, DiagnosticId id, params string[] args)
        {
            WarnVersion version = GetWarningVersion();
            MessageContainer warning = MessageContainer.CreateWarningMessage(this, origin, id, version, args);
            _cachedWarningMessageContainers.Add(warning);
        }

        /// <summary>
        /// Display a warning message to the end user.
        /// This API is used for warnings defined in ILLink, not by custom steps. Warning
        /// versions are inferred from the code, and every warning that we define is versioned.
        /// </summary>
        /// <param name="text">Humanly readable message describing the warning</param>
        /// <param name="code">Unique warning ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of warnings and possibly add a new one</param>
        /// <param name="origin">Type or member where the warning is coming from</param>
        /// <param name="subcategory">Optionally, further categorize this warning</param>
        internal void LogWarning(string text, int code, IMemberDefinition origin, int ilOffset = MessageOrigin.UnsetILOffset, string subcategory = MessageSubCategory.None)
        {
            MessageOrigin _origin = new MessageOrigin(origin, ilOffset);
            LogWarning(text, code, _origin, subcategory);
        }

        /// <summary>
        /// Display a warning message to the end user.
        /// This API is used for warnings defined in ILLink, not by custom steps. Warning
        /// versions are inferred from the code, and every warning that we define is versioned.
        /// </summary>
        /// <param name="origin">Type or member where the warning is coming from</param>
        /// <param name="id">Unique warning ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of warnings and possibly add a new one</param>
        /// <param name="args">Additional arguments to form a humanly readable message describing the warning</param>
        internal void LogWarning(IMemberDefinition origin, DiagnosticId id, int ilOffset = MessageOrigin.UnsetILOffset, params string[] args)
        {
            MessageOrigin _origin = new MessageOrigin(origin, ilOffset);
            LogWarning(_origin, id, args);
        }

        /// <summary>
        /// Display a warning message to the end user.
        /// This API is used for warnings defined in ILLink, not by custom steps. Warning
        /// versions are inferred from the code, and every warning that we define is versioned.
        /// </summary>
        /// <param name="origin">Type or member where the warning is coming from</param>
        /// <param name="id">Unique warning ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of warnings and possibly add a new one</param>
        /// <param name="args">Additional arguments to form a humanly readable message describing the warning</param>
        public void LogWarning(IMemberDefinition origin, DiagnosticId id, params string[] args)
        {
            MessageOrigin _origin = new MessageOrigin(origin);
            LogWarning(_origin, id, args);
        }

        /// <summary>
        /// Display a warning message to the end user.
        /// This API is used for warnings defined in ILLink, not by custom steps. Warning
        /// versions are inferred from the code, and every warning that we define is versioned.
        /// </summary>
        /// <param name="text">Humanly readable message describing the warning</param>
        /// <param name="code">Unique warning ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of warnings and possibly add a new one</param>
        /// <param name="origin">Filename where the warning is coming from</param>
        /// <param name="subcategory">Optionally, further categorize this warning</param>
        public void LogWarning(string text, int code, string origin, string subcategory = MessageSubCategory.None)
        {
            MessageOrigin _origin = new MessageOrigin(origin);
            LogWarning(text, code, _origin, subcategory);
        }

        /// <summary>
        /// Display a warning message to the end user.
        /// This API is used for warnings defined in ILLink, not by custom steps. Warning
        /// versions are inferred from the code, and every warning that we define is versioned.
        /// </summary>
        /// <param name="origin">Filename where the warning is coming from</param>
        /// <param name="id">Unique warning ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of warnings and possibly add a new one</param>
        /// <param name="args">Additional arguments to form a humanly readable message describing the warning</param>
        public void LogWarning(string origin, DiagnosticId id, params string[] args)
        {
            MessageOrigin _origin = new MessageOrigin(origin);
            LogWarning(_origin, id, args);
        }

        /// <summary>
        /// Display an error message to the end user.
        /// </summary>
        /// <param name="text">Humanly readable message describing the error</param>
        /// <param name="code">Unique error ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md for the list of errors and possibly add a new one</param>
        /// <param name="subcategory">Optionally, further categorize this error</param>
        /// <param name="origin">Filename, line, and column where the error was found</param>
        public void LogError(string text, int code, string subcategory = MessageSubCategory.None, MessageOrigin? origin = null)
        {
            var error = MessageContainer.CreateErrorMessage(text, code, subcategory, origin);
            LogMessage(error);
        }

        /// <summary>
        /// Display an error message to the end user.
        /// </summary>
        /// <param name="origin">Filename, line, and column where the error was found</param>
        /// <param name="id">Unique error ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md and https://github.com/dotnet/runtime/blob/main/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs for the list of errors and possibly add a new one</param>
        /// <param name="args">Additional arguments to form a humanly readable message describing the warning</param>
        public void LogError(MessageOrigin? origin, DiagnosticId id, params string[] args)
        {
            var error = MessageContainer.CreateErrorMessage(origin, id, args);
            LogMessage(error);
        }

        /// <summary>
        /// Throws a LinkerFatalErrorException
        /// </summary>
        /// <param name="text">Humanly readable message describing the error</param>
        /// <param name="code">Unique error ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md
        /// for the list of errors and possibly add a new one</param>
        /// <param name="subcategory">Optionally, further categorize this error</param>
        /// <param name="origin">Filename, line, and column where the error was found</param>
        public static void FatalError(string text, int code, string subcategory = MessageSubCategory.None, MessageOrigin? origin = null)
        {
            throw new LinkerFatalErrorException(MessageContainer.CreateErrorMessage(text, code, subcategory, origin));
        }

        /// <summary>
        /// Throws a LinkerFatalErrorException
        /// </summary>
        /// <param name="text">Humanly readable message describing the error</param>
        /// <param name="code">Unique error ID. Please see https://github.com/dotnet/runtime/blob/main/docs/tools/illink/error-codes.md
        /// for the list of errors and possibly add a new one</param>
        /// <param name="subcategory">Optionally, further categorize this error</param>
        /// <param name="origin">Filename, line, and column where the error was found</param>
        /// <param name="innerException">Optional, an inner exception</param>
        public static void FatalError(string text, int code, Exception innerException, string subcategory = MessageSubCategory.None, MessageOrigin? origin = null)
        {
            throw new LinkerFatalErrorException(MessageContainer.CreateErrorMessage(text, code, subcategory, origin), innerException);
        }

        public void FlushCachedWarnings()
        {
            _cachedWarningMessageContainers.Sort();
            foreach (var warning in _cachedWarningMessageContainers)
                LogMessage(warning);

            _cachedWarningMessageContainers.Clear();
        }

        public bool IsWarningSuppressed(int warningCode, string subcategory, MessageOrigin origin)
        {
            if (subcategory == MessageSubCategory.TrimAnalysis && NoTrimWarn)
                return true;

            // This warning was turned off by --nowarn.
            if (NoWarn.Contains(warningCode))
                return true;

            if (Suppressions == null)
                return false;

            return Suppressions.IsSuppressed(warningCode, origin, out _);
        }

        public bool IsWarningAsError(int warningCode)
        {
            bool value;
            if (GeneralWarnAsError)
                return !WarnAsError.TryGetValue(warningCode, out value) || value;

            return WarnAsError.TryGetValue(warningCode, out value) && value;
        }

        public bool IsSingleWarn(string assemblyName)
        {
            bool value;
            if (GeneralSingleWarn)
                return !SingleWarn.TryGetValue(assemblyName, out value) || value;

            return SingleWarn.TryGetValue(assemblyName, out value) && value;
        }

        static WarnVersion GetWarningVersion()
        {
            // This should return an increasing WarnVersion for new warning waves.
            return WarnVersion.ILLink5;
        }

        public int GetTargetRuntimeVersion()
        {
            if (_targetRuntime != null)
                return _targetRuntime.Value;

            TypeDefinition? objectType = BCL.FindPredefinedType(WellKnownType.System_Object, this);
            _targetRuntime = objectType?.Module.Assembly.Name.Version.Major ?? -1;

            return _targetRuntime.Value;
        }

        readonly Dictionary<MethodReference, MethodDefinition?> methodresolveCache = new();
        readonly Dictionary<FieldReference, FieldDefinition?> fieldresolveCache = new();
        readonly Dictionary<TypeReference, TypeDefinition?> typeresolveCache = new();
        readonly Dictionary<ExportedType, TypeDefinition?> exportedTypeResolveCache = new();

        /// <summary>
        /// Tries to resolve the MethodReference to a MethodDefinition and logs a warning if it can't
        /// </summary>
        public MethodDefinition? Resolve(MethodReference methodReference)
        {
            if (methodReference is MethodDefinition methodDefinition)
                return methodDefinition;

            if (methodReference is null)
                return null;

            if (methodresolveCache.TryGetValue(methodReference, out MethodDefinition? md))
            {
                if (md == null && !IgnoreUnresolved)
                    ReportUnresolved(methodReference);
                return md;
            }

#pragma warning disable RS0030 // Cecil's resolve is banned -- this provides the wrapper
            md = methodReference.Resolve();
#pragma warning restore RS0030
            if (md == null && !IgnoreUnresolved)
                ReportUnresolved(methodReference);

            methodresolveCache.Add(methodReference, md);
            return md;
        }

        /// <summary>
        /// Tries to resolve the MethodReference to a MethodDefinition and returns null if it can't
        /// </summary>
        public MethodDefinition? TryResolve(MethodReference methodReference)
        {
            if (methodReference is MethodDefinition methodDefinition)
                return methodDefinition;

            if (methodReference is null)
                return null;

            if (methodresolveCache.TryGetValue(methodReference, out MethodDefinition? md))
                return md;

#pragma warning disable RS0030 // Cecil's resolve is banned -- this method provides the wrapper
            md = methodReference.Resolve();
#pragma warning restore RS0030
            methodresolveCache.Add(methodReference, md);
            return md;
        }

        /// <summary>
        /// Tries to resolve the FieldReference to a FieldDefinition and logs a warning if it can't
        /// </summary>
        public FieldDefinition? Resolve(FieldReference fieldReference)
        {
            if (fieldReference is FieldDefinition fieldDefinition)
                return fieldDefinition;

            if (fieldReference is null)
                return null;

            if (fieldresolveCache.TryGetValue(fieldReference, out FieldDefinition? fd))
            {
                if (fd == null && !IgnoreUnresolved)
                    ReportUnresolved(fieldReference);
                return fd;
            }

            fd = fieldReference.Resolve();
            if (fd == null && !IgnoreUnresolved)
                ReportUnresolved(fieldReference);

            fieldresolveCache.Add(fieldReference, fd);
            return fd;
        }

        /// <summary>
        /// Tries to resolve the FieldReference to a FieldDefinition and returns null if it can't
        /// </summary>
        public FieldDefinition? TryResolve(FieldReference fieldReference)
        {
            if (fieldReference is FieldDefinition fieldDefinition)
                return fieldDefinition;

            if (fieldReference is null)
                return null;

            if (fieldresolveCache.TryGetValue(fieldReference, out FieldDefinition? fd))
                return fd;

            fd = fieldReference.Resolve();
            fieldresolveCache.Add(fieldReference, fd);
            return fd;
        }

        /// <summary>
        /// Tries to resolve the TypeReference to a TypeDefinition and logs a warning if it can't
        /// </summary>
        public TypeDefinition? Resolve(TypeReference typeReference)
        {
            if (typeReference is TypeDefinition typeDefinition)
                return typeDefinition;

            if (typeReference is null)
                return null;

            if (typeresolveCache.TryGetValue(typeReference, out TypeDefinition? td))
            {
                if (td == null && !IgnoreUnresolved)
                    ReportUnresolved(typeReference);
                return td;
            }

            //
            // Types which never have TypeDefinition or can have ambiguous definition should not be passed in
            //
            if (typeReference is GenericParameter || (typeReference is TypeSpecification && typeReference is not GenericInstanceType))
                throw new NotSupportedException($"TypeDefinition cannot be resolved from '{typeReference.GetType()}' type");

#pragma warning disable RS0030
            td = typeReference.Resolve();
#pragma warning restore RS0030
            if (td == null && !IgnoreUnresolved)
                ReportUnresolved(typeReference);

            typeresolveCache.Add(typeReference, td);
            return td;
        }

        /// <summary>
        /// Tries to resolve the TypeReference to a TypeDefinition and returns null if it can't
        /// </summary>
        public TypeDefinition? TryResolve(TypeReference typeReference)
        {
            if (typeReference is TypeDefinition typeDefinition)
                return typeDefinition;

            if (typeReference is null || typeReference is GenericParameter)
                return null;

            if (typeresolveCache.TryGetValue(typeReference, out TypeDefinition? td))
                return td;

            if (typeReference is TypeSpecification ts)
            {
                if (typeReference is FunctionPointerType)
                {
                    td = null;
                }
                else
                {
                    //
                    // It returns element-type for arrays and also element type for wrapping types like ByReference, PinnedType, etc
                    //
                    td = TryResolve(ts.GetElementType());
                }
            }
            else
            {
#pragma warning disable RS0030
                td = typeReference.Resolve();
#pragma warning restore RS0030
            }

            typeresolveCache.Add(typeReference, td);
            return td;
        }

        /// <summary>
        /// Tries to resolve the ExportedType to a TypeDefinition and logs a warning if it can't
        /// </summary>
        public TypeDefinition? Resolve(ExportedType et)
        {
            if (TryResolve(et) is not TypeDefinition td)
            {
                ReportUnresolved(et);
                return null;
            }
            return td;
        }

        /// <summary>
        /// Tries to resolve the ExportedType to a TypeDefinition and returns null if it can't
        /// </summary>
        public TypeDefinition? TryResolve(ExportedType et)
        {
            if (exportedTypeResolveCache.TryGetValue(et, out var td))
            {
                return td;
            }
#pragma warning disable RS0030 // Cecil's Resolve is banned -- this method provides the wrapper
            td = et.Resolve();
#pragma warning restore RS0030
            exportedTypeResolveCache.Add(et, td);
            return td;
        }

        public TypeDefinition? TryResolve(AssemblyDefinition assembly, string typeNameString)
        {
            // It could be cached if it shows up on fast path
            return TypeNameResolver.TryResolveTypeName(assembly, typeNameString, out TypeReference? typeReference, out _)
                ? TryResolve(typeReference)
                : null;
        }

        readonly HashSet<MethodDefinition> _processed_bodies_for_method = new HashSet<MethodDefinition>(2048);

        /// <summary>
        /// ILLink applies some optimization on method bodies. For example it can remove dead branches of code
        /// based on constant propagation. To avoid overmarking, all code which processes the method's IL
        /// should only view the IL after it's been optimized.
        /// As such typically MethodDefinition.MethodBody should not be accessed directly on the Cecil object model
        /// instead all accesses to method body should go through the ILProvider here
        /// which will make sure the IL of the method is fully optimized before it's handed out.
        /// </summary>
        public MethodIL GetMethodIL(Cecil.Cil.MethodBody methodBody)
            => GetMethodIL(methodBody.Method);

        public MethodIL GetMethodIL(MethodDefinition method)
        {
            if (_processed_bodies_for_method.Add(method))
            {
                _unreachableBlocksOptimizer.ProcessMethod(method);
            }

            return MethodIL.Create(method.Body);
        }

        readonly HashSet<MemberReference> unresolved_reported = new();

        readonly HashSet<ExportedType> unresolved_exported_types_reported = new();

        protected virtual void ReportUnresolved(FieldReference fieldReference)
        {
            if (unresolved_reported.Add(fieldReference))
                LogError(string.Format(SharedStrings.FailedToResolveFieldElementMessage, fieldReference.FullName), (int)DiagnosticId.FailedToResolveMetadataElement);
        }

        protected virtual void ReportUnresolved(MethodReference methodReference)
        {
            if (unresolved_reported.Add(methodReference))
                LogError(string.Format(SharedStrings.FailedToResolveMethodElementMessage, methodReference.GetDisplayName()), (int)DiagnosticId.FailedToResolveMetadataElement);
        }

        protected virtual void ReportUnresolved(TypeReference typeReference)
        {
            if (unresolved_reported.Add(typeReference))
                LogError(string.Format(SharedStrings.FailedToResolveTypeElementMessage, typeReference.GetDisplayName()), (int)DiagnosticId.FailedToResolveMetadataElement);
        }

        protected virtual void ReportUnresolved(ExportedType et)
        {
            if (unresolved_exported_types_reported.Add(et))
                LogError(string.Format(SharedStrings.FailedToResolveTypeElementMessage, et.Name), (int)DiagnosticId.FailedToResolveMetadataElement);
        }
    }

    public class CodeOptimizationsSettings
    {
        sealed class Pair
        {
            public Pair(CodeOptimizations set, CodeOptimizations values)
            {
                this.Set = set;
                this.Values = values;
            }

            public CodeOptimizations Set;
            public CodeOptimizations Values;
        }

        readonly Dictionary<string, Pair> perAssembly = new();

        public CodeOptimizationsSettings(CodeOptimizations globalOptimizations)
        {
            Global = globalOptimizations;
        }

        public CodeOptimizations Global { get; private set; }

        internal bool IsEnabled(CodeOptimizations optimizations, AssemblyDefinition? context)
        {
            return IsEnabled(optimizations, context?.Name.Name);
        }

        public bool IsEnabled(CodeOptimizations optimizations, string? assemblyName)
        {
            // Only one bit is set
            Debug.Assert(optimizations != 0 && (optimizations & (optimizations - 1)) == 0);

            if (perAssembly.Count > 0 && assemblyName != null &&
                perAssembly.TryGetValue(assemblyName, out var assemblySetting) &&
                (assemblySetting.Set & optimizations) != 0)
            {
                return (assemblySetting.Values & optimizations) != 0;
            }

            return (Global & optimizations) != 0;
        }

        public void Enable(CodeOptimizations optimizations, string? assemblyContext = null)
        {
            if (assemblyContext == null)
            {
                Global |= optimizations;
                return;
            }

            if (!perAssembly.TryGetValue(assemblyContext, out var assemblySetting))
            {
                perAssembly.Add(assemblyContext, new Pair(optimizations, optimizations));
                return;
            }

            assemblySetting.Set |= optimizations;
            assemblySetting.Values |= optimizations;
        }

        public void Disable(CodeOptimizations optimizations, string? assemblyContext = null)
        {
            if (assemblyContext == null)
            {
                Global &= ~optimizations;
                return;
            }

            if (!perAssembly.TryGetValue(assemblyContext, out var assemblySetting))
            {
                perAssembly.Add(assemblyContext, new Pair(optimizations, 0));
                return;
            }

            assemblySetting.Set |= optimizations;
            assemblySetting.Values &= ~optimizations;
        }
    }

    [Flags]
    public enum CodeOptimizations
    {
        BeforeFieldInit = 1 << 0,

        /// <summary>
        /// Option to disable removal of overrides of virtual methods when a type is never instantiated
        ///
        /// Being able to disable this optimization is helpful when trying to troubleshoot problems caused by types created via reflection or from native
        /// that do not get an instance constructor marked.
        /// </summary>
        OverrideRemoval = 1 << 1,

        /// <summary>
        /// Option to disable delaying marking of instance methods until an instance of that type could exist
        /// </summary>
        UnreachableBodies = 1 << 2,

        /// <summary>
        /// Option to remove .interfaceimpl for interface types that are not used
        /// </summary>
        UnusedInterfaces = 1 << 3,

        /// <summary>
        /// Option to do interprocedural constant propagation on return values
        /// </summary>
        IPConstantPropagation = 1 << 4,

        /// <summary>
        /// Devirtualizes methods and seals types
        /// </summary>
        Sealer = 1 << 5,

        /// <summary>
        /// Option to inline typechecks for never instantiated types
        /// </summary>
        UnusedTypeChecks = 1 << 6,


        RemoveDescriptors = 1 << 20,
        RemoveSubstitutions = 1 << 21,
        RemoveLinkAttributes = 1 << 22,
        RemoveDynamicDependencyAttribute = 1 << 23,

        /// <summary>
        /// Option to apply annotations to type heirarchy
        /// Enable type heirarchy apply in library mode to annotate derived types eagerly
        /// Otherwise, type annotation will only be applied with calls to object.GetType()
        /// </summary>
        OptimizeTypeHierarchyAnnotations = 1 << 24,

        /// <summary>
        /// Option to substitute properties annotated as FeatureGuard(typeof(RequiresUnreferencedCodeAttribute)) with false
        /// </summary>
        SubstituteFeatureGuards = 1 << 25,
    }
}
